/*
Coder: Dzlua
Email: 505544956@qq.com
Time : 2017/05/18
*/
#pragma once

#include <string>
#include <iostream>

#include "sock.h"
#include "common.h"

#define ESAY_SETGET(arg, argtype, funname) \
    void Set##funname(const argtype& arggive) {\
        arg = arggive;\
    }\
    argtype Get##funname() {\
        return arg;\
    }
#define EmptyStr ""

class CmdTask
{
public:
    enum CmdType {
        CMDTYPE_NULL = 0,
        CMDTYPE_SHELL = 1,
    };

    enum OutputType {
        OUTPUTTYPE_INFO, OUTPUTTYPE_WARNING, OUTPUTTYPE_ERROR,
    };

    CmdTask()
        : m_type(EmptyStr)
        , m_host(EmptyStr)
        , m_port(-1)
        , m_cmd(CMDTYPE_NULL)
        , m_args(EmptyStr) {
    }

    CmdTask(  const std::string& type
            , const std::string& host
            , int port
            , CmdType cmd
            , const std::string& args)
                : m_type(type)
                , m_host(host)
                , m_port(port)
                , m_cmd(cmd)
                , m_args(args) {

    }

    ESAY_SETGET(m_type, std::string, Type)
    ESAY_SETGET(m_host, std::string, Host)
    ESAY_SETGET(m_port, int, Port)
    ESAY_SETGET(m_cmd, CmdType, CmdType)
    ESAY_SETGET(m_args, std::string, Args)

    std::string GetHostName() {
        std::string hostname = m_type + "://" + m_host + ":" + std::to_string(m_port);
        return std::move(hostname);
    }

    bool SetHostName(const std::string& host) {
        std::vector<std::string> v;
        std::string data;
        Common::Split(host, v, "://");
        if (v.size() > 0)
            SetType(v.at(0));
        if (v.size() > 1)
            data = v.at(1);
        v.clear();

        Common::Split(data, v, ":");
        if (v.size() > 0)
            SetHost(v.at(0));
        if (v.size() > 1)
            SetPort( std::stoi(v.at(1)) );

        return true;
    }

    // 
    bool Run() {
        switch (m_cmd) {
            case CmdType::CMDTYPE_NULL : break;
            case CmdType::CMDTYPE_SHELL : return RunShell();
            default : break;
        }
        return true;
    }

    void Send() {
        std::string host = GetHostName();
        if (host.empty()) return;
        
        if (!m_client.Init(m_host, m_port))
            return;
        m_client.Send(this);
    }

    std::string Receive() {
        return std::move(m_client.Receive());
    }

    std::string Serialization() {
        std::string data;
        data += GetHostName();
        data += "?";
        data += std::to_string((int)m_cmd);
        data += "=";
        data += m_args;
        return std::move(Common::Base64Encode(data));
    }

    bool Deserialization(const std::string& base64data) {
        if (base64data.empty()) return false;
        std::string data = Common::Base64Decode(base64data);
        if (data.empty()) return false;

        std::vector<std::string> v;
        Common::Split(data, v, "?");
        if (v.size() <= 0) return false;
        if (v.size() > 0)
            if (!SetHostName( v.at(0) ))
                return false;
        if (v.size() > 1)
            data = v.at(1);

        if (data.empty()) return true;
        v.clear();
        Common::Split(data, v, "=");
        if (v.size() > 0)
            SetCmdType( (CmdType)(std::stoi( v.at(0) )));
        if (v.size() > 1)
            SetArgs(v.at(1));

        return true;
    }

    void Clear() {
        m_type.clear();
        m_host.clear();
        m_args.clear();
        m_port = -1;
        m_cmd = CMDTYPE_NULL;
    }
protected:
    void Output(OutputType type, const std::string& msg) {
        std::string typmsg;
        switch (type) {
            case OutputType::OUTPUTTYPE_INFO : typmsg = "INFO."; break;
            case OutputType::OUTPUTTYPE_WARNING : typmsg = "WARNING."; break;
            case OutputType::OUTPUTTYPE_ERROR : typmsg = "ERROR."; break;
        }
        std::cout << typmsg << msg << std::endl;
    }
    bool RunShell() {
        std::string exe, args;
        {
            std::string s = Common::Trim(m_args);
            std::vector<std::string> v;
            Common::Split(s, v, " ");

            int sz = v.size();
            if (sz > 0)
                exe = v.at(0);
            if (sz > 1) {
                auto it = v.begin();
                v.erase(it);
                args = Common::UnSplit(v, " ");
            }
        }

        if (exe.empty()) {
            Output(OutputType::OUTPUTTYPE_ERROR, "Please give a shell command.");
            return false;
        }

        Common::ExecuteShell(exe, args);

        return true;
    }
protected:
    std::string m_type;
    std::string m_host;
    int m_port;
    CmdType m_cmd;
    std::string m_args;
    Client m_client;
};